package com.cman777.springc.idempotence;

import com.cman777.springc.common.bean.exception.BusinessException;
import com.cman777.springc.common.helper.AopUtil;
import com.cman777.springc.common.helper.SpelUtil;
import com.cman777.springc.common.helper.ValidateUtil;
import com.cman777.springc.idempotence.annotation.IdempotenceId;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.Validate;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Optional;

/**
 * @author chenzhicong
 * @time 2020/9/15 11:31
 */
@Aspect
@Log4j2
public class IdempotenceAspect {

    private IdempotenceClient idempotenceClient;

    public IdempotenceAspect(IdempotenceClient idempotenceClient) {
        this.idempotenceClient = idempotenceClient;
    }

    /**
     * 定义切面
     */
    @Pointcut("@annotation(com.cman777.springc.idempotence.annotation.IdempotenceId)")
    public void idempotence() {
    }

    /**
     * 如果有幂等键存在则返回同样的返回值或抛出同样的异常，或抛出一个固定异常
     */
    @Around("idempotence()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        Object[] args = proceedingJoinPoint.getArgs();
        Signature signature = proceedingJoinPoint.getSignature();
        Validate.isTrue(signature instanceof MethodSignature);
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        IdempotenceId idempotenceIdA = Optional.of(method.getAnnotation(IdempotenceId.class)).get();
        String idempotenceId = idempotenceIdA.prefix() + SpelUtil.parse(idempotenceIdA.salt(), method, args);
        log.debug("幂等校验,幂等键={}", idempotenceId);
        ResultWrapper[] resultWrapperFlag = new ResultWrapper[]{null};
        boolean[] resultType =  new boolean[]{false};
        boolean isExists = !idempotenceClient.saveIfAbsent(idempotenceId, () -> {
            ResultWrapper resultWrapper = new ResultWrapper();
            Object result = null;
            try {
                result = proceedingJoinPoint.proceed(args);
            } catch (Throwable e) {
                resultWrapper.setHasException(true);
                resultWrapper.setException(e);
            }
            try {
                resultWrapper.setResult((Serializable) result);
            } catch (ClassCastException e) {
                resultType[0] = true;
                log.error("class={}必须实现序列化接口", result.getClass().getName());
            }
            resultWrapperFlag[0]=resultWrapper;
            return resultWrapper;
        });
        if (!isExists) {
            if (resultType[0]) {
                log.error("{}方法返回值必须实现序列化接口,幂等框架失效", method.getName());
                idempotenceClient.delete(idempotenceId);
                return resultWrapperFlag[0].getResult();
            }
            log.debug("幂等通过，幂等键={}", idempotenceId);
            if (resultWrapperFlag[0].isHasException()) {
                //如果有异常，由上层业务控制异常之后是否删除幂等键
                boolean needDelete = true;
                for (Class clz : idempotenceIdA.notDeleteForException()) {
                    if (clz.isAssignableFrom(resultWrapperFlag[0].getException().getClass())) {
                        needDelete = false;
                    }
                }
                if (needDelete) {
                    log.error("发生异常={}，删除幂等键={}", resultWrapperFlag[0].getException().getMessage(), idempotenceId);
                    idempotenceClient.delete(idempotenceId);
                }
                throw resultWrapperFlag[0].getException();
            } else {
                return resultWrapperFlag[0].getResult();
            }
        } else {
            log.debug("幂等校验失败,幂等键={}",idempotenceId);
            if (idempotenceIdA.strategy() == IdempotenceId.Strategy.THROW_EXCEPTION) {
                throw new BusinessException("幂等校验失败");
            }
            ValidateUtil.isTrue(idempotenceIdA.strategy() == IdempotenceId.Strategy.RETURN_RESULT,"SYSTEM_ERROR","增了枚举这里要改动");
            ResultWrapper resultWrapper = idempotenceClient.getResult(idempotenceId);
            if (resultWrapper.isHasException()) {
                log.info("请注意，这里返回的是幂等的异常，如果打印了栈信息则不是真实的栈信息！");
                throw resultWrapper.getException();
            } else {
                return resultWrapper.getResult();
            }
        }


    }

}
